package cn.sc.summer.core.config.security;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.StrUtil;
import cn.sc.summer.constant.mybatisplus.TenantInfo;
import cn.sc.summer.constant.mybatisplus.context.TenantContextHolder;
import cn.sc.summer.constant.mybatisplus.properties.TenantProperties;
import cn.sc.summer.constant.project.ProjectConstant;
import cn.sc.summer.constant.token.AuthProperties;
import cn.sc.summer.constant.token.TokenConstant;
import cn.sc.summer.constant.util.MatchUtil;
import cn.sc.summer.core.user.SpringSecurityUserUtil;
import cn.sc.summer.exception.hodler.SpringContextHolder;
import cn.sc.summer.redis.util.RouteUtil;
import cn.sc.summer.token.base.DefaultLoginToken;
import cn.sc.summer.token.encrypt.TokenUtil;
import cn.sc.summer.token.enums.RequestTypeEnum;
import cn.sc.summer.token.po.UserDetailX;
import cn.sc.summer.token.po.UserTokenInfo;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Optional;
import java.util.function.Supplier;

/**
 * 类名：校验用户访问资源是否合法且有权限
 *
 * @author a-xin
 * @date 2024/9/18 17:31
 */
@Slf4j
@Component
public class AuthorizationServerManagerX implements AuthorizationManager<RequestAuthorizationContext> {

    private static final String[] IGNORE_URL = {
    };

    @Resource
    private AuthProperties authProperties;

    @Resource
    private TenantProperties tenantProperties;

    /**
     * Determines if access is granted for a specific authentication and object.
     *
     * @param authentication              the {@link Supplier} of the {@link Authentication} to check
     * @param requestAuthorizationContext the object to check
     * @return an {@link AuthorizationDecision} or null if no decision could be made
     */
    @Override
    public AuthorizationDecision check(Supplier<Authentication> authentication, RequestAuthorizationContext requestAuthorizationContext) {

        HttpServletRequest request = requestAuthorizationContext.getRequest();
        SpringContextHolder.setHttpServletRequest(request);

        String requestURI = request.getRequestURI();
        String rawPath = RouteUtil.getRawPath(ProjectConstant.SERVER_NAME, requestURI);
        if (CharSequenceUtil.isBlank(rawPath)) {
            throw new AccessDeniedException("Access Denied");
        }
        log.debug("-> resource path : {} -> {}", requestURI, rawPath);

        //自定义忽略路径
        if (MatchUtil.anyMatchValue(rawPath, IGNORE_URL)) {
            return new AuthorizationDecision(true);
        }

        String apiKey = request.getHeader(TokenConstant.API_KEY_REQUEST_HEADER);
        String requestType = request.getHeader(TokenConstant.REQUEST_TYPE);

        Boolean requestCheck = requestCheck(rawPath, apiKey, requestType);
        if (!requestCheck) {
            throw new AccessDeniedException("Access Denied");
        }
        Boolean check = authorizationCheck(rawPath);
        if (!check) {
            throw new AccessDeniedException("Access Denied");
        }
        return new AuthorizationDecision(true);
    }

    /**
     * 请求校验
     *
     * @param rawPath     原始路径
     * @param apiKey      api访问秘钥
     * @param requestType 请求类型
     * @return 是否通过校验
     */
    protected Boolean requestCheck(String rawPath, String apiKey, String requestType) {

        //判断是否忽略swagger资源路径
        if (authProperties.getSwagger() && MatchUtil.anyMatchValue(rawPath, authProperties.getSwaggerUrl())) {
            return Boolean.TRUE;
        }

        //判断是否忽略资源路径
        if (authProperties.getResource() && MatchUtil.anyMatchValue(rawPath, authProperties.getResourceUrl())) {
            return Boolean.TRUE;
        }

        if (StrUtil.isBlank(apiKey) || StrUtil.isBlank(requestType)) {
            return Boolean.FALSE;
        }

        Optional<RequestTypeEnum> byType = Optional.ofNullable(RequestTypeEnum.getByType(requestType));
        if (!byType.isPresent()) {
            return Boolean.FALSE;
        }

        Boolean tokenCheck = TokenUtil.analysisApiEncryptKey(apiKey, byType.get());
        if (!tokenCheck) {
            return Boolean.FALSE;
        }

        return Boolean.TRUE;
    }

    /**
     * 请求token校验
     *
     * @param rawPath 原始路径
     * @return 是否通过校验
     */
    protected Boolean authorizationCheck(String rawPath) {

        //判断是都匹配忽略权限校验路径
        if (MatchUtil.anyMatchValue(rawPath, authProperties.getAuthIgnore())) {
            return Boolean.TRUE;
        }

        //判断是否忽略swagger资源路径
        if (authProperties.getSwagger() && MatchUtil.anyMatchValue(rawPath, authProperties.getSwaggerUrl())) {
            return Boolean.TRUE;
        }

        //判断是否忽略资源路径
        if (authProperties.getResource() && MatchUtil.anyMatchValue(rawPath, authProperties.getResourceUrl())) {
            return Boolean.TRUE;
        }

        //是否存在登录态
        if (!SpringSecurityUserUtil.hasLoginStatus()) {
            return Boolean.FALSE;
        }

        UserDetailX userDetailX = SpringSecurityUserUtil.getLoginUser();
        UserTokenInfo loginInfo = SpringSecurityUserUtil.getLoginInfo();
        String authenticationStr = loginInfo.getAuthentication();
        Authentication userAuthentication = JSONObject.parseObject(authenticationStr, Authentication.class);

        //获取权限信息，封装到Authentication中，存入SecurityContextHolder
        DefaultLoginToken authenticationToken = new DefaultLoginToken(userAuthentication.getPrincipal(),
                userAuthentication.getCredentials(),
                userDetailX.getAuthorities());
        authenticationToken.setUserDetailX(userDetailX);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);

        TenantInfo tenantInfo = new TenantInfo();
        tenantInfo.setIgnoreTenant(Boolean.TRUE);
        TenantContextHolder.setTenantInfo(tenantInfo);

        //排除需要校验token的，否则会token种获取租户ID
        if (MatchUtil.anyMatchValue(rawPath, authProperties.getAuthIgnore())
                || MatchUtil.anyMatchValue(rawPath, authProperties.getTenantIgnore())) {
            return Boolean.TRUE;
        }

        if (tenantProperties.getEnabled()) {
            String loginUserTenantId = SpringSecurityUserUtil.getLoginUserTenantId();
            tenantInfo.setIgnoreTenant(Boolean.FALSE);
            tenantInfo.setTenantId(loginUserTenantId);
            TenantContextHolder.setTenantInfo(tenantInfo);
        }

        return Boolean.TRUE;
    }

}
